home *** CD-ROM | disk | FTP | other *** search
/ Night Owl 6 / Night Owl's Shareware - PDSI-006 - Night Owl Corp (1990).iso / 020a / dcfg202.zip / DCFG2.PAS < prev    next >
Pascal/Delphi Source File  |  1992-01-24  |  19KB  |  632 lines

  1.  
  2. {Dirt Cheap Frame Grabber - Version 2.02}
  3. {as of 5 January 1992 - by Michael Day}
  4. {public domain}
  5.  
  6. program DCFG2;
  7. uses crt;
  8. const maxframe = 30000;
  9.       maxintrp = 30000;
  10.  
  11. type frametype = array[0..maxframe] of byte;
  12.      frameptr = ^frametype;
  13.      intrptype = array[0..maxintrp] of byte;
  14.      intrpptr = ^intrptype;
  15.      string8 = string[8];
  16.  
  17.      FrameObj = object
  18.        fary : array[0..3] of frameptr;
  19.        iary : intrpptr;
  20.        dary : intrpptr;
  21.        inport : word;      {frame port data input address (video data)}
  22.        outport : word;     {frame port data output address (control)}
  23.        frameport : word;   {printer port number to use for frame grabber}
  24.        grabsize : word;    {size of data to grab from port}
  25.        framenum : byte;    {frame sequence number}
  26.        IntrpWidth : word;  {width of the intrp array (scan width) }
  27.        IntrpSize : word;   {size of the intrp array (width*lines) }
  28.        Filenum:word;       {next file frame number to use}
  29.        DiskFrameSize:word;
  30.        FrameCount:word;
  31.  
  32.        constructor Init;
  33.        destructor Done;
  34.        procedure SetFramePort(what:string8);
  35.        function  GrabFrame(inprt,size:word; Fptr:frameptr):boolean;
  36.        function  GrabOne:boolean;
  37.        procedure F2IConvert(Fnum:byte; GSize,IWidth,ISize:word;
  38.                             Iptr:IntrpPtr; Fptr:FramePtr);
  39.        procedure IntrpDisplay(fnum,IWidth,ISize:word; Iptr:IntrpPtr);
  40.        procedure MakeDiskArray(fnum,IWidth,ISize:word;
  41.                                     Iptr:IntrpPtr; Dptr:IntrpPtr);
  42.      end;
  43.  
  44. var  Frame : FrameObj;
  45.      prnarray : array[0..3] of word absolute $40:$08;
  46.      screen : array[0..65520] of byte absolute $A000:0;
  47.  
  48.      crtmode : byte absolute $40:$49;
  49.      oldmode : byte;
  50.      i:word;
  51.      ib:byte;
  52.      cx:char;
  53.      mf:file;
  54.      filenum:word;
  55.      showframe : boolean;
  56.      fns:string;
  57.      MovieEnabled:boolean;
  58.  
  59.  
  60. {-----------------------------------------------------------}
  61. {     gray level interpretation chart                       }
  62. {                                                           }
  63. {          frame data                                       }
  64. {gray    F3  F2  F1  F0   F3 = frame 3, F2 = frame 2        }
  65. {level:  76  54  32  10   F1 = frame 1, F0 = frame 0        }
  66. {   12:  11  xx  xx  xx   each group of two bits            }
  67. {   11: <11  11  xx  xx   represent the video level         }
  68. {   10: <11 <11  11  xx   for the frame indicated           }
  69. {    9: <11 <11 <11  11                                     }
  70. {    8:  10 <11 <11 <11   xx = any bit pattern              }
  71. {    7: <10  10 <11 <11   <11 = less than 11; (10, 01, 00)  }
  72. {    6: <10 <10  10 <11   <10 = less than 10; (01 or 00)    }
  73. {    5: <10 <10 <10  10   11, 10, 01, or 00 = the indicated }
  74. {    4:  01 <10 <10 <10                absolute bit pattern }
  75. {    3:  00  01 <10 <10                                     }
  76. {    2:  00  00  01 <10   the gray level for the specified  }
  77. {    1:  00  00  00  01   bit pattern is shown at the left  }
  78. {    0:  00  00  00  00                                     }
  79. {-----------------------------------------------------------}
  80. {this array is used to translate from the interpretation    }
  81. {array data into a gray level for display on the screen     }
  82. const IntrpXlat : array[0..255] of byte = (
  83.     0,1,5,9,2,2,5,9,         6,6,6,9,10,10,10,10,
  84.     3,3,5,9,3,3,5,9,         6,6,6,9,10,10,10,10,
  85.     7,7,7,9,7,7,7,9,         7,7,7,9,10,10,10,10,
  86.     11,11,11,11,11,11,11,11, 11,11,11,11,11,11,11,11,
  87.     4,4,5,9,4,4,5,9,         6,6,6,9,10,10,10,10,
  88.     4,4,5,9,4,4,5,9,         6,6,6,9,10,10,10,10,
  89.     7,7,7,9,7,7,7,9,         7,7,7,9,10,10,10,10,
  90.     11,11,11,11,11,11,11,11, 11,11,11,11,11,11,11,11,
  91.     8,8,8,9,8,8,8,9,         8,8,8,9,10,10,10,10,
  92.     8,8,8,9,8,8,8,9,         8,8,8,9,10,10,10,10,
  93.     8,8,8,9,8,8,8,9,         8,8,8,9,10,10,10,10,
  94.     11,11,11,11,11,11,11,11, 11,11,11,11,11,11,11,11,
  95.     12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,
  96.     12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,
  97.     12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12,
  98.     12,12,12,12,12,12,12,12, 12,12,12,12,12,12,12,12);
  99.  
  100. {-----------------------------------------------------------}
  101.  
  102.  
  103.     {grab a chunk of video from inprt size bytes in length into fary}
  104.     function FrameObj.GrabFrame(inprt,size:word; Fptr:frameptr):boolean; assembler;
  105.      asm
  106.       mov bx,17000      {timeout if we go over 50ms without sync}
  107.       mov dx,[inprt]
  108.       les di,[Fptr]     {now collect a frame}
  109.       mov cx,0
  110.  
  111.      @vsloop1:
  112.       mov ah,8 {[vsyncslice]}  {if we are in a vert sync, get out of it first}
  113.      @vsloop2:
  114.       dec bx
  115.       jz @vdone
  116.       in al,dx
  117.       shl al,1
  118.       jc @vsloop1
  119.       dec ah
  120.       jnz @vsloop2
  121.  
  122.      @vsloop3:
  123.       mov ah,8 {[vsyncslice]}  {find the start of a vert sync}
  124.      @vsloop4:
  125.       dec bx
  126.       jz @vdone
  127.       in al,dx
  128.       shl al,1
  129.       jnc @vsloop3
  130.       dec ah
  131.       jnz @vsloop4
  132.  
  133.       mov cx,[size]      {start collecting data}
  134.       rep
  135.       db 6ch
  136.  
  137.      @vdone:
  138.       xor al,al        {return error code}
  139.       or bh,bl         {one = all ok}
  140.       jz @vexit        {zero = no sync}
  141.       inc al
  142.      @vexit:
  143.     end;
  144.  
  145.  
  146.   Constructor FrameObj.Init;
  147.   var i:byte;
  148.   begin
  149.  
  150.     for i := 0 to 3 do
  151.     begin
  152.       new(fary[i]);
  153.       fillchar(fary[i]^,sizeof(fary[i]^),0);
  154.     end;
  155.     new(iary);
  156.     fillchar(iary^,sizeof(iary^),0);
  157.     move(IntrpXlat,iary^,256);
  158.     new(dary);
  159.     fillchar(dary^,sizeof(dary^),0);
  160.     move(IntrpXlat,dary^,256);
  161.   end;
  162.  
  163.  
  164.   Destructor FrameObj.Done;
  165.   var i:byte;
  166.   begin
  167.     for i := 0 to 3 do
  168.     begin
  169.       dispose(fary[i]);
  170.     end;
  171.     dispose(iary);
  172.     dispose(dary);
  173.   end;
  174.  
  175.  
  176.   procedure FrameObj.SetFramePort(what:string8);
  177.   begin
  178.     frameport := 0;
  179.     if length(what) > 0 then
  180.     begin
  181.       case what[1] of
  182.        '2': frameport := 1;
  183.        '3': frameport := 2;
  184.        '4': frameport := 3;
  185.       end;
  186.     end;
  187.     outport := prnarray[frameport]; {- $378}  {get port base addr}
  188.     inport := outport+1;    {- $379}
  189.  
  190.     port[outport+2] := $04; {- $37A}   {init output control lines}
  191.     port[outport] := $ff;    {init data lines}
  192.     grabsize := 20000;           {default grab size}
  193.     framenum := 0;
  194.     IntrpWidth := 70;
  195.     IntrpSize := IntrpWidth*(262-12);
  196.     framecount := 0;
  197.    end;
  198.  
  199.  
  200.  
  201.   function FrameObj.GrabOne:boolean;
  202.   var Fptr : framePtr;
  203.   begin
  204.     inc(framenum);
  205.     framenum := framenum and 3;
  206.     port[frame.outport] := (framenum shl 6) or $3f;
  207.     Fptr := fary[framenum];
  208.     asm CLI; end;
  209.     GrabOne := GrabFrame(inport,grabsize,Fptr);
  210.     asm STI; end;
  211.     port[frame.outport] := $3f;
  212.   end;
  213.  
  214.  
  215.  
  216.    {==================================================================}
  217.    {note: this assumes that the frame grab array has been preformated}
  218.    {with starting with a valid scan line at the top of the screen}
  219.    procedure FrameObj.F2Iconvert(Fnum:byte; GSize,IWidth,ISize:word;
  220.                                  Iptr:IntrpPtr; Fptr:FramePtr);
  221.    var Bottom:word;
  222.    begin
  223.      asm
  224.        mov cl,ss:[Fnum]      {get gray scale frame number}
  225.        and cl,03H
  226.        add cl,cl             {*2 = shifter count}
  227.        mov ch,0FCH           {create intrp data mask}
  228.        rol ch,cl
  229.        mov dx,ss:[GSize]     {get size of grabbed data to convert}
  230.        inc dx
  231.        les di,ss:[Iptr]      {get intrp array pointer}
  232.        add di,256            {first 256 bytes has xlat array}
  233.        mov ax,di
  234.        add ax,ss:[ISize]     {compute intrp bottom address offset}
  235.        mov ss:[Bottom],ax    {and save it}
  236.        mov bx,ss:[IWidth]    {put intrp right edge offset into bx}
  237.  
  238.        push ds               {save current data segment}
  239.        lds si,ss:[Fptr]      {get video frame pointer to DS:SI}
  240.        add si,500            {ignore the vertical sync}
  241.  
  242.      {data conversion loop starts here}
  243.      @loop1:
  244.        dec dx           {did we run out of data?}
  245.        jz @done
  246.        lodsb            {get a frame scan byte}
  247.        shl al,1         {if it is a sync, try again}
  248.        jc @loop1
  249.  
  250.      @loop2:
  251.        dec dx           {did we run out of data?}
  252.        jz @done
  253.        lodsb            {get a frame scan byte}
  254.        shl al,1         {if it is a sync, we are}
  255.        jc @loop4        {done with the scan line}
  256.  
  257.        {convert scan input data to intrp level reference}
  258.        xor ah,ah        {init to zero level}
  259.        shl al,1         {if highest level on}
  260.        adc ah,0         {add one to level count}
  261.        shl al,1         {if next high level on}
  262.        adc ah,0         {add one to level count}
  263.        shl al,1         {if lowest level on}
  264.        adc ah,0         {add one to level count}
  265.        shl ah,cl        {adjust result to position}
  266.        mov al,es:[di]   {get current intrp value}
  267.        and al,ch        {strip old intrp value}
  268.        or al,ah         {insert new intrp value}
  269.        mov es:[di],al   {save the new intrp value}
  270.        inc di
  271.        dec bx           {if not at end of intrp line}
  272.        jnz @loop2       {go process the next byte}
  273.  
  274.      {ran against right edge of intrp window}
  275.      {so throw away rest of the scan data}
  276.      @loop3:            {suck up extra scan data}
  277.        dec dx           {did we run out of data?}
  278.        jz @done
  279.        lodsb            {get a frame scan byte}
  280.        shl al,1         {if it is not a sync, }
  281.        jnc @loop3       {keep looping}
  282.        jmp @loopd
  283.  
  284.      @loop4:            {fill out rest of intrp data}
  285.        and es:[di],ch   {strip old intrp value to 0}
  286.        inc di
  287.        dec bx           {loop until right edge reached}
  288.        jnz @loop4
  289.  
  290.      @loopd:
  291.        mov bx,ss:[IWidth]    {restore width to reg BX}
  292.        cmp di,ss:[Bottom]    {are we at bottom?}
  293.        jc @loop1             {do more if not at bottom}
  294.  
  295.      @done:
  296.        pop ds             {restore DS and we are done}
  297.      end;
  298.    end;
  299.  
  300.  
  301. {=====================================================================}
  302.    {now we are gonna display the video on the screen}
  303.    procedure FrameObj.IntrpDisplay(fnum,IWidth,ISize:word; Iptr:IntrpPtr);
  304.    var Bottom:word;
  305.    begin
  306.      asm
  307.        push ds
  308.        lds si,ss:[Iptr]      {get intrp array pointer}
  309.        mov bx,si             {point bx at the start of the array}
  310.        add si,256            {first 256 bytes has intpr array}
  311.        mov ax,ss:[ISize]     {compute intrp bottom address offset}
  312.        add ax,si
  313.        mov ss:[Bottom],ax    {and save it}
  314.        mov ax,0A000h         {point es to the display segment}
  315.        mov es,ax
  316.        mov cx,ss:[IWidth]    {put intrp right edge offset}
  317.        mov di,fnum           {start at top left corner of screen}
  318.        and di,1              {offset by frame number count (even/odd)}
  319.        jz @dlp1
  320.        add si,cx             {use odd scan lines on odd video frames}
  321.  
  322.      @dlp1:
  323.        push di
  324.      @dlp2:
  325.        lodsb          {get a intrp byte}
  326.        xlat           {translate it to gray scale number}
  327.        stosb          {display it}
  328.        inc di         {skip a display pixel (we get it next time)}
  329.        dec cx         {end of the scan line?}
  330.        jnz @dlp2      {loop until done}
  331.        pop di         {restore original display start offset}
  332.        add di,320     {add display width to it}
  333.        mov cx,ss:[IWidth]  {restore Iwidth to cx}
  334.        add si,cx
  335.        add si,cx           {skip three video scan lines}
  336.        add si,cx
  337.        cmp si,ss:[Bottom]  {are we at the bottom?}
  338.        jc @dlp1            {keep going if not}
  339.  
  340.      @done:
  341.        pop ds          {ok, we're done}
  342.      end;
  343.    end;
  344.  
  345.  
  346. {=====================================================================}
  347.    {now we are gonna display the video on the screen}
  348.    procedure FrameObj.MakeDiskArray(fnum,IWidth,ISize:word;
  349.                                     Iptr:IntrpPtr; Dptr:IntrpPtr);
  350.    var Bottom:word;
  351.    begin
  352.      asm
  353.        push ds
  354.        lds si,ss:[Iptr]      {get intrp array pointer}
  355.        mov bx,si             {point bx at the start of the array}
  356.        add si,256            {first 256 bytes has intpr array}
  357.        mov ax,ss:[ISize]     {compute intrp bottom address offset}
  358.        add ax,si
  359.        mov ss:[Bottom],ax    {and save it}
  360.        les di,Dptr           {point es:di at the disk array}
  361.        mov cx,ss:[IWidth]    {put intrp right edge offset}
  362.        mov dx,si
  363.        add dx,cx
  364.  
  365.      @dlp1:
  366.        lodsb          {get a intrp byte}
  367.        xlat           {translate it to gray scale number}
  368.        mov ah,al
  369.        xchg si,dx
  370.        lodsb
  371.        xlat
  372.        xchg ah,al
  373.        xchg si,dx
  374.        stosw          {save it in the array}
  375.        dec cx         {end of the scan line?}
  376.        jnz @dlp1      {loop until done}
  377.  
  378.      @dlp3:
  379.        mov cx,ss:[IWidth]  {restore Iwidth to cx}
  380.        add si,cx
  381.        add si,cx           {skip three video scan lines}
  382.        add si,cx
  383.        mov dx,si
  384.        add dx,cx
  385.        cmp si,ss:[Bottom]  {are we at the bottom?}
  386.        jc @dlp1            {keep going if not}
  387.  
  388.      @done:
  389.        pop ds          {ok, we're done}
  390.      end;
  391.    end;
  392.  
  393.  
  394. procedure DisplayMovieFrame(DWidth,DSize:word; Dptr:IntrpPtr);
  395. var Bottom:word;
  396. begin
  397.      asm
  398.        push ds
  399.        lds si,ss:[Dptr]      {get intrp array pointer}
  400.        mov ax,ss:[DSize]     {compute intrp bottom address offset}
  401.        add ax,si
  402.        mov ss:[Bottom],ax    {and save it}
  403.        mov ax,0A000h         {point es to the display segment}
  404.        mov es,ax
  405.        mov di,0
  406.        mov cx,ss:[DWidth]    {put intrp right edge offset}
  407.  
  408.      @dlp1:
  409.        push di
  410.        rep movsb      {get a movie byte and display it}
  411.        pop di         {restore original display start offset}
  412.        add di,320     {add display width to it}
  413.        mov cx,ss:[DWidth]  {restore Iwidth to cx}
  414.        cmp si,ss:[Bottom]  {are we at the bottom?}
  415.        jc @dlp1            {keep going if not}
  416.  
  417.      @done:
  418.        pop ds          {ok, we're done}
  419.      end;
  420. end;
  421.  
  422. {================================================================}
  423. function fstr(W:word):string8;
  424. var s:string8;
  425. begin
  426.   str(W,S);
  427.   fstr := S;
  428. end;
  429.  
  430.  
  431. {------------------------------------------------------------}
  432. {format of disk file is:                                     }
  433. {       number of frames : word                              }
  434. {    frame size in bytes : word                              }
  435. {   frame width in bytes : word                              }
  436. {       video frame data : array[0..frames] of dary^         }
  437. {------------------------------------------------------------}
  438. procedure OpenMovie;
  439. var MovieWidth : word;
  440.     MovieSize  : word;
  441.     MovieCount : word;
  442. begin
  443.   Frame.FrameCount := 0;
  444.   if Frame.filenum > 9 then frame.filenum := 0;
  445.   MovieWidth := Frame.IntrpWidth*2;
  446.   MovieSize := (Frame.IntrpSize*2) div 3;
  447.   MovieCount := Frame.Framecount;
  448.   fns := 'DCFG'+fstr(Frame.filenum)+'.MOV';
  449.   Assign(mf,fns);
  450.   inc(Frame.filenum);
  451.   rewrite(mf,1);
  452.   blockwrite(mf,MovieCount,2);
  453.   blockwrite(mf,MovieSize,2);
  454.   blockwrite(mf,MovieWidth,2);
  455. end;
  456. procedure WriteMovie;
  457. var MovieSize:word;
  458. begin
  459.   MovieSize := (Frame.IntrpSize*2) div 3;
  460.   inc(frame.framecount);
  461.   Frame.MakeDiskArray(Frame.framenum,Frame.IntrpWidth,
  462.                       Frame.IntrpSize, Frame.Iary, Frame.Dary);
  463.   blockwrite(mf,Frame.Dary^,MovieSize);
  464. end;
  465. procedure CloseMovie;
  466. begin
  467.   reset(mf,1);
  468.   dec(Frame.FrameCount);
  469.   blockwrite(mf,Frame.framecount,2);
  470.   close(mf);
  471. end;
  472.  
  473. procedure ShowMovie(what:char; Rep:boolean);
  474. var MovieWidth:word;
  475.     MovieSize:word;
  476.     MovieCount:word;
  477.     done:boolean;
  478. begin
  479.   showframe := false;
  480.   done := false;
  481.   While not(done) do
  482.   begin
  483.     if not(Rep) then fns := 'DCFG'+what+'.MOV';
  484.     Assign(mf,fns);
  485.     reset(mf,1);
  486.     blockread(mf,MovieCount,2);
  487.     blockread(mf,MovieSize,2);
  488.     blockread(mf,MovieWidth,2);
  489.     inc(MovieCount);
  490.     i := 0;
  491.     while i < MovieCount do
  492.     begin
  493.       blockread(mf,Frame.Dary^,MovieSize);
  494.       DisplayMovieFrame(MovieWidth,MovieSize,Frame.Dary);
  495.       if keypressed then i := MovieCount;
  496.       gotoxy(1,24);
  497.       write('Showing Movie:',fns,' Frame:',i,' ');
  498.       inc(i);
  499.       delay(50);
  500.     end;
  501.     close(mf);
  502.     if not(Rep) then done := true;
  503.     if keypressed then done := true;
  504.   end;
  505.   gotoxy(1,24);
  506.   write('                                  ');
  507. end;
  508.  
  509.  
  510. procedure SaveToFrame;
  511. begin
  512.   OpenMovie;
  513.   WriteMovie;
  514.   CloseMovie;
  515. end;
  516.  
  517.  
  518. { ************************************************************** }
  519. { program start }
  520.  
  521. begin
  522.    writeln;
  523.    cx := #255;
  524.    filenum := 0;
  525.    showframe := true;
  526.    MovieEnabled := false;
  527.  
  528.    directvideo := false;
  529.  
  530.    OldMode := CrtMode;
  531.    asm
  532.      mov ax,$0013    {switch to vga graphics mode}
  533.      mov bx,0
  534.      int $10
  535.    end;
  536.  
  537.    ib := 0;
  538.    while ib < 15 do    {load palettes with gray levels}
  539.    begin
  540.      asm
  541.        mov ax,1010h
  542.        mov ch,[ib]      {green}
  543.        add ch,ch
  544.        add ch,ch
  545.        mov cl,ch      {blue}
  546.        mov dh,ch      {red}
  547.        mov bl,[ib]
  548.        mov bh,0
  549.        int 10h
  550.      end;
  551.      inc(ib);
  552.    end;
  553.  
  554.    fillchar(screen,sizeof(screen),0);
  555.  
  556.  
  557.    Frame.Init;
  558.    if ParamCount > 0 then
  559.      Frame.SetFramePort(ParamStr(1))
  560.    else
  561.      Frame.SetFramePort('1');
  562.  
  563.    gotoxy(1,20);
  564.    write('X:',Frame.IntrpWidth * 2,' Y:',Frame.IntrpSize div (Frame.Intrpwidth *2),'   ');
  565.  
  566.  repeat
  567.    if Frame.GrabOne then
  568.    begin
  569.       Frame.F2Iconvert(Frame.Framenum,Frame.GrabSize,
  570.                        Frame.IntrpWidth,Frame.IntrpSize,
  571.                        Frame.Iary, Frame.Fary[Frame.framenum]);
  572.  
  573.      if MovieEnabled then
  574.      begin
  575.        WriteMovie;
  576.        gotoxy(1,24);
  577.        write('Movie:',fns,' Frame:',Frame.framecount,'  ');
  578.        gotoxy(1,25);
  579.        write('Movie on  ');
  580.      end
  581.      else
  582.      begin
  583.        gotoxy(1,25);
  584.        write('Movie off ');
  585.      end;
  586.      gotoxy(1,22);
  587.      write('          ');
  588.  
  589.    end
  590.    else
  591.    begin
  592.      gotoxy(1,22);
  593.      write('Lost Sync');
  594.    end;
  595.  
  596.    if ShowFrame then
  597.      Frame.IntrpDisplay(Frame.framenum,Frame.IntrpWidth,
  598.                         Frame.IntrpSize,Frame.Iary);
  599.  
  600.  
  601.    if keypressed then   {key pressed? If so, process it}
  602.         begin
  603.           cx := readkey;
  604.           if cx = #0 then cx := char($80+ord(readkey));
  605.           if MovieEnabled then
  606.           begin
  607.             MovieEnabled := false;
  608.             CloseMovie;
  609.           end;
  610.  
  611.                if upcase(cx) = 'F'then SaveToFrame
  612.           else if upcase(cx) = 'M' then begin OpenMovie; MovieEnabled := true; end
  613.           else if upcase(cx) = 'R' then ShowMovie(cx,true)
  614.           else if upcase(cx) = 'S' then Showframe := false
  615.           else if (cx >= '0') and (cx <= '9') then ShowMovie(cx,false)
  616.           else showframe := true;
  617.  
  618.           gotoxy(1,20);
  619.           write('X:',Frame.IntrpWidth * 2,' Y:',Frame.IntrpSize div (Frame.Intrpwidth *2),'   ');
  620.         end;
  621.  
  622.    until cx < #32;
  623.  
  624.    asm
  625.      mov ah,$00        {restore original display mode}
  626.      mov al,[oldmode]
  627.      mov bx,0
  628.      int $10
  629.    end;
  630.  
  631. end.
  632.